",
options: {
disabled: false,
// callbacks
create: null
},
_createWidget: function( options, element ) {
element = $( element || this.defaultElement || this )[ 0 ];
this.element = $( element );
this.uuid = uuid++;
this.eventNamespace = "." + this.widgetName + this.uuid;
this.options = $.widget.extend( {},
this.options,
this._getCreateOptions(),
options );
this.bindings = $();
this.hoverable = $();
this.focusable = $();
if ( element !== this ) {
$.data( element, this.widgetFullName, this );
this._on( true, this.element, {
remove: function( event ) {
if ( event.target === element ) {
this.destroy();
}
}
});
this.document = $( element.style ?
// element within the document
element.ownerDocument :
// element is window or document
element.document || element );
this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
}
this._create();
this._trigger( "create", null, this._getCreateEventData() );
this._init();
},
_getCreateOptions: $.noop,
_getCreateEventData: $.noop,
_create: $.noop,
_init: $.noop,
destroy: function() {
this._destroy();
// we can probably remove the unbind calls in 2.0
// all event bindings should go through this._on()
this.element
.unbind( this.eventNamespace )
.removeData( this.widgetFullName )
// support: jquery <1.6.3
// http://bugs.jquery.com/ticket/9413
.removeData( $.camelCase( this.widgetFullName ) );
this.widget()
.unbind( this.eventNamespace )
.removeAttr( "aria-disabled" )
.removeClass(
this.widgetFullName + "-disabled " +
"ui-state-disabled" );
// clean up events and states
this.bindings.unbind( this.eventNamespace );
this.hoverable.removeClass( "ui-state-hover" );
this.focusable.removeClass( "ui-state-focus" );
},
_destroy: $.noop,
widget: function() {
return this.element;
},
option: function( key, value ) {
var options = key,
parts,
curOption,
i;
if ( arguments.length === 0 ) {
// don't return a reference to the internal hash
return $.widget.extend( {}, this.options );
}
if ( typeof key === "string" ) {
// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
options = {};
parts = key.split( "." );
key = parts.shift();
if ( parts.length ) {
curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
for ( i = 0; i < parts.length - 1; i++ ) {
curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
curOption = curOption[ parts[ i ] ];
}
key = parts.pop();
if ( value === undefined ) {
return curOption[ key ] === undefined ? null : curOption[ key ];
}
curOption[ key ] = value;
} else {
if ( value === undefined ) {
return this.options[ key ] === undefined ? null : this.options[ key ];
}
options[ key ] = value;
}
}
this._setOptions( options );
return this;
},
_setOptions: function( options ) {
var key;
for ( key in options ) {
this._setOption( key, options[ key ] );
}
return this;
},
_setOption: function( key, value ) {
this.options[ key ] = value;
if ( key === "disabled" ) {
this.widget()
.toggleClass( this.widgetFullName + "-disabled", !!value );
this.hoverable.removeClass( "ui-state-hover" );
this.focusable.removeClass( "ui-state-focus" );
}
return this;
},
enable: function() {
return this._setOptions({ disabled: false });
},
disable: function() {
return this._setOptions({ disabled: true });
},
_on: function( suppressDisabledCheck, element, handlers ) {
var delegateElement,
instance = this;
// no suppressDisabledCheck flag, shuffle arguments
if ( typeof suppressDisabledCheck !== "boolean" ) {
handlers = element;
element = suppressDisabledCheck;
suppressDisabledCheck = false;
}
// no element argument, shuffle and use this.element
if ( !handlers ) {
handlers = element;
element = this.element;
delegateElement = this.widget();
} else {
// accept selectors, DOM elements
element = delegateElement = $( element );
this.bindings = this.bindings.add( element );
}
$.each( handlers, function( event, handler ) {
function handlerProxy() {
// allow widgets to customize the disabled handling
// - disabled as an array instead of boolean
// - disabled class as method for disabling individual parts
if ( !suppressDisabledCheck &&
( instance.options.disabled === true ||
$( this ).hasClass( "ui-state-disabled" ) ) ) {
return;
}
return ( typeof handler === "string" ? instance[ handler ] : handler )
.apply( instance, arguments );
}
// copy the guid so direct unbinding works
if ( typeof handler !== "string" ) {
handlerProxy.guid = handler.guid =
handler.guid || handlerProxy.guid || $.guid++;
}
var match = event.match( /^(\w+)\s*(.*)$/ ),
eventName = match[1] + instance.eventNamespace,
selector = match[2];
if ( selector ) {
delegateElement.delegate( selector, eventName, handlerProxy );
} else {
element.bind( eventName, handlerProxy );
}
});
},
_off: function( element, eventName ) {
eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
element.unbind( eventName ).undelegate( eventName );
},
_delay: function( handler, delay ) {
function handlerProxy() {
return ( typeof handler === "string" ? instance[ handler ] : handler )
.apply( instance, arguments );
}
var instance = this;
return setTimeout( handlerProxy, delay || 0 );
},
_hoverable: function( element ) {
this.hoverable = this.hoverable.add( element );
this._on( element, {
mouseenter: function( event ) {
$( event.currentTarget ).addClass( "ui-state-hover" );
},
mouseleave: function( event ) {
$( event.currentTarget ).removeClass( "ui-state-hover" );
}
});
},
_focusable: function( element ) {
this.focusable = this.focusable.add( element );
this._on( element, {
focusin: function( event ) {
$( event.currentTarget ).addClass( "ui-state-focus" );
},
focusout: function( event ) {
$( event.currentTarget ).removeClass( "ui-state-focus" );
}
});
},
_trigger: function( type, event, data ) {
var prop, orig,
callback = this.options[ type ];
data = data || {};
event = $.Event( event );
event.type = ( type === this.widgetEventPrefix ?
type :
this.widgetEventPrefix + type ).toLowerCase();
// the original event may come from any element
// so we need to reset the target on the new event
event.target = this.element[ 0 ];
// copy original event properties over to the new event
orig = event.originalEvent;
if ( orig ) {
for ( prop in orig ) {
if ( !( prop in event ) ) {
event[ prop ] = orig[ prop ];
}
}
}
this.element.trigger( event, data );
return !( $.isFunction( callback ) &&
callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
event.isDefaultPrevented() );
}
};
$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
if ( typeof options === "string" ) {
options = { effect: options };
}
var hasOptions,
effectName = !options ?
method :
options === true || typeof options === "number" ?
defaultEffect :
options.effect || defaultEffect;
options = options || {};
if ( typeof options === "number" ) {
options = { duration: options };
}
hasOptions = !$.isEmptyObject( options );
options.complete = callback;
if ( options.delay ) {
element.delay( options.delay );
}
if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
element[ method ]( options );
} else if ( effectName !== method && element[ effectName ] ) {
element[ effectName ]( options.duration, options.easing, callback );
} else {
element.queue(function( next ) {
$( this )[ method ]();
if ( callback ) {
callback.call( element[ 0 ] );
}
next();
});
}
};
});
})( jQuery );
;
/*
* jQuery Mobile Framework : events
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function ($, undefined) {
$.attrFn = $.attrFn || {};
// add new event shortcuts
$.each("touchstart touchmove touchend orientationchange tap taphold swipe swipeleft swiperight scrollstart scrollstop".split(" "), function (i, name) {
$.fn[ name ] = function (fn) {
return fn ? this.bind(name, fn) : this.trigger(name);
};
$.attrFn[ name ] = true;
});
var supportTouch = ('ontouchstart' in window),
scrollEvent = "touchmove scroll",
touchStartEvent = supportTouch ? "touchstart" : "mousedown",
touchStopEvent = supportTouch ? "touchend" : "mouseup",
touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
// also handles scrollstop
$.event.special.scrollstart = {
enabled: true,
setup: function () {
var thisObject = this,
$this = $(thisObject),
scrolling,
timer;
function trigger(event, state) {
scrolling = state;
var originalType = event.type;
event.type = scrolling ? "scrollstart" : "scrollstop";
$.event.handle.call(thisObject, event);
event.type = originalType;
}
// iPhone triggers scroll after a small delay; use touchmove instead
$this.bind(scrollEvent, function (event) {
if (!$.event.special.scrollstart.enabled) {
return;
}
if (!scrolling) {
trigger(event, true);
}
clearTimeout(timer);
timer = setTimeout(function () {
trigger(event, false);
}, 50);
});
}
};
// also handles taphold
$.event.special.tap = {
setup: function () {
var thisObject = this,
$this = $(thisObject);
$this
.bind(touchStartEvent, function (event) {
if (event.which && event.which !== 1 ||
//check if event fired once already by a device that fires both mousedown and touchstart (while supporting both events)
$this.data("prevEvent") && $this.data("prevEvent") !== event.type) {
return false;
}
//save event type so only this type is let through for a temp duration,
//allowing quick repetitive taps but not duplicative events
$this.data("prevEvent", event.type);
setTimeout(function () {
$this.removeData("prevEvent");
}, 800);
var moved = false,
touching = true,
origTarget = event.target,
origEvent = event.originalEvent,
origPos = event.type == "touchstart" ? [origEvent.touches[0].pageX, origEvent.touches[0].pageY] : [ event.pageX, event.pageY ],
originalType,
timer;
function moveHandler(event) {
if (event.type == "scroll") {
moved = true;
return;
}
var newPageXY = event.type == "touchmove" ? event.originalEvent.touches[0] : event;
if ((Math.abs(origPos[0] - newPageXY.pageX) > 10) ||
(Math.abs(origPos[1] - newPageXY.pageY) > 10)) {
moved = true;
}
}
timer = setTimeout(function () {
if (touching && !moved) {
originalType = event.type;
event.type = "taphold";
$.event.handle.call(thisObject, event);
event.type = originalType;
}
}, 750);
//scroll now cancels tap
$(window).one("scroll", moveHandler);
$this
.bind(touchMoveEvent, moveHandler)
.one(touchStopEvent, function (event) {
$this.unbind(touchMoveEvent, moveHandler);
$(window).unbind("scroll", moveHandler);
clearTimeout(timer);
touching = false;
// ONLY trigger a 'tap' event if the start target is
// the same as the stop target.
if (!moved && ( origTarget == event.target )) {
originalType = event.type;
event.type = "tap";
$.event.handle.call(thisObject, event);
event.type = originalType;
}
});
});
}
};
// also handles swipeleft, swiperight
$.event.special.swipe = {
setup: function () {
var thisObject = this,
$this = $(thisObject);
$this
.bind(touchStartEvent, function (event) {
var data = event.originalEvent.touches ?
event.originalEvent.touches[ 0 ] :
event,
start = {
time: (new Date).getTime(),
coords: [ data.pageX, data.pageY ],
origin: $(event.target)
},
stop;
function moveHandler(event) {
if (!start) {
return;
}
var data = event.originalEvent.touches ?
event.originalEvent.touches[ 0 ] :
event;
stop = {
time: (new Date).getTime(),
coords: [ data.pageX, data.pageY ]
};
// prevent scrolling
if (Math.abs(start.coords[0] - stop.coords[0]) > 10) {
event.preventDefault();
}
}
$this
.bind(touchMoveEvent, moveHandler)
.one(touchStopEvent, function (event) {
$this.unbind(touchMoveEvent, moveHandler);
if (start && stop) {
if (stop.time - start.time < 1000 &&
Math.abs(start.coords[0] - stop.coords[0]) > 30 &&
Math.abs(start.coords[1] - stop.coords[1]) < 75) {
start.origin
.trigger("swipe")
.trigger(start.coords[0] > stop.coords[0] ? "swipeleft" : "swiperight");
}
}
start = stop = undefined;
});
});
}
};
(function ($) {
// "Cowboy" Ben Alman
var win = $(window),
special_event,
get_orientation,
last_orientation;
$.event.special.orientationchange = special_event = {
setup: function () {
// If the event is supported natively, return false so that jQuery
// will bind to the event using DOM methods.
if ($.support.orientation) {
return false;
}
// Get the current orientation to avoid initial double-triggering.
last_orientation = get_orientation();
// Because the orientationchange event doesn't exist, simulate the
// event by testing window dimensions on resize.
win.bind("resize", handler);
},
teardown: function () {
// If the event is not supported natively, return false so that
// jQuery will unbind the event using DOM methods.
if ($.support.orientation) {
return false;
}
// Because the orientationchange event doesn't exist, unbind the
// resize event handler.
win.unbind("resize", handler);
},
add: function (handleObj) {
// Save a reference to the bound event handler.
var old_handler = handleObj.handler;
handleObj.handler = function (event) {
// Modify event object, adding the .orientation property.
event.orientation = get_orientation();
// Call the originally-bound event handler and return its result.
return old_handler.apply(this, arguments);
};
}
};
// If the event is not supported natively, this handler will be bound to
// the window resize event to simulate the orientationchange event.
function handler() {
// Get the current orientation.
var orientation = get_orientation();
if (orientation !== last_orientation) {
// The orientation has changed, so trigger the orientationchange event.
last_orientation = orientation;
win.trigger("orientationchange");
}
};
// Get the current page orientation. This method is exposed publicly, should it
// be needed, as jQuery.event.special.orientationchange.orientation()
special_event.orientation = get_orientation = function () {
var elem = document.documentElement;
return elem && elem.clientWidth / elem.clientHeight < 1.1 ? "portrait" : "landscape";
};
})(jQuery);
$.each({
scrollstop: "scrollstart",
taphold: "tap",
swipeleft: "swipe",
swiperight: "swipe"
}, function (event, sourceEvent) {
$.event.special[ event ] = {
setup: function () {
$(this).bind(sourceEvent, $.noop);
}
};
});
})(jQuery);
;
(function ($, window) {
// tracking for clicking on free video link to the player site
$(document).ready(function() {
// Trigger a Google Analytics _trackEvent for click events on elements with
// the .ga-track-click-event class. Parameters for the event will be extracted
// from data attributes on the element.
$(document).on('click', '.ga-track-click-event', function(e) {
if ($(this).data('event-category')) {
var category = $(this).data('event-category');
}
if ($(this).data('event-action')) {
var action = $(this).data('event-action');
}
if ($(this).data('event-label')) {
var label = $(this).data('event-label');
}
if ($(this).data('event-value')) {
var value = $(this).data('event-value');
}
_gaq.push(['_trackEvent', category, action, label, value, true]);
});
$(document).on('click', '.mg-bfiplayer-video-tab .slide-caption a', function() {
_gaq.push(['_trackEvent', 'BFI Player click-through', 'Click', 'free - player site', , true]);
});
/**
* Event tracking for header and footer.
*/
$(document).on('click', '.header a, .footer a', function(e) {
var linkTitle = $(this).text();
var linkHref = $(this).attr('href');
var eventAction;
var bounceRateCalculation = false;
// If the link is just a hash (such as the text size links) then the event hit will not be used in bounce-rate calculation.
if (linkHref == '#') {
bounceRateCalculation = true;
}
if ($(this).is('.header a')) {
eventAction = 'Header link click';
} else if ($(this).is('.footer a')) {
eventAction = 'Footer link click';
}
_gaq.push(['_trackEvent', 'Header/footer click-through', eventAction, linkTitle, , bounceRateCalculation]);
});
});
/**
* function that removes the css defined height of lazy-loaded images and placeholders,
* to prevent 'stretching' on browser resize.
*/
window.flexImageFix = function() {
$('img.lazy-loaded, img.lazy-hidden').each( function() {
$(this).css('height', '');
});
};
window.fixIe8LastChild = function() {
$('[class*="column"] + [class*="column"]:last').addClass('last-child');
$('.button-group :last-child, .bfig .thumbnails > li a:last-child, p:last-child, .screenings li:last-child, .pinned td:last-child').addClass('last-child');
};
window.bfiResize = function() {
if (window.resizeStatus != 'inProgress'){
window.resizeStatus = 'inProgress';
$('#bfi-gallery').gallery('reset');
setTimeout("window.resizeStatus = 'done';", 500);
flexImageFix();
$('body.with-hero-banner').heroBanner('resetBanner');
$('.equalise[class*=-block-grid-]').equalHeight('equalise', true);
$('.twitter-feed:first').twitterFeed('reset');
//$('.layout-item :bfi-equalHeight').equalHeight('equalise');
/** --- Active Breakpoint Custom Event --- **/
// get the current breakpoint label from window object
current_breakpoint = window.currentBreakpoint;
// get the details for the active breakpoint
new_breakpoint = getBreakpoint();
/**
* check if label for the new breakpoint matches the old one.
* if not, either a new breakpoint has been activated or the page has just been loaded
* current_breakpoint on page load is undefined
*/
if(current_breakpoint != new_breakpoint.label) {
// assign the active breakpoint label to a window property
window.currentBreakpoint = new_breakpoint.label;
// add / update the body class with current label
if (typeof current_breakpoint === "undefined") {
$( "body" ).addClass( new_breakpoint.label );
}
else {
$( "body" ).removeClass( current_breakpoint ).addClass( new_breakpoint.label );
}
/**
* trigger the change active breakpoint event, passing the lower, upper bounds and label properties
* as arguments to the event listener
*/
$( document ).trigger({
type: "changeActiveBreakpoint",
lower_bound: new_breakpoint.lower_bound,
upper_bound: new_breakpoint.upper_bound,
label: new_breakpoint.label
});
}
/** --- End Active Breakpoint Custom Event --- **/
}
};
/**
* this function gets the current active breakpoint, based on the available media queries
* and current browser resolution and returns a set of properties related to it.
* @return {object} breakpoint
* Available properties:
* - breakpoint.lower_bound - the lower bound from the current breakpoint
* - breakpoint.upper_bound - the upper bound from the current breakpoint
* - breakpoint.label - label assigned to the breakpoint (x-small, small, medium, large, x-large, xx-large)
*/
window.getBreakpoint = function() {
var breakpoint = [];
if (Modernizr.mq('only screen and (max-width: 320px)')) {
breakpoint.lower_bound = 0;
breakpoint.upper_bound = 320;
breakpoint.label = 'x-small';
}
if (Modernizr.mq('only screen and (min-width: 321px) and (max-width: 480px)')) {
breakpoint.lower_bound = 321;
breakpoint.upper_bound = 480;
breakpoint.label = 'small';
}
if (Modernizr.mq('only screen and (min-width: 481px) and (max-width: 767px)')) {
breakpoint.lower_bound = 481;
breakpoint.upper_bound = 767;
breakpoint.label = 'medium';
}
if (Modernizr.mq('only screen and (min-width: 768px) and (max-width: 1023px)')) {
breakpoint.lower_bound = 768;
breakpoint.upper_bound = 1023;
breakpoint.label = 'large';
}
if (Modernizr.mq('only screen and (min-width: 1024px) and (max-width: 1439px)')) {
breakpoint.lower_bound = 1024;
breakpoint.upper_bound = 1439;
breakpoint.label = 'x-large';
}
if (Modernizr.mq('only screen and (min-width: 1440px)')) {
breakpoint.lower_bound = 1440;
breakpoint.upper_bound = 999999;
breakpoint.label = 'xx-large';
}
return breakpoint;
};
/* +++ equalHeight +++ */
$.widget('bfi.equalHeight', {
options: {
child: '> li .box',
perRow: true
},
_create: function() {
var $element = this.element, so = this.options;
for (var opt in so) {
if ( (typeof $element.attr('data-' + opt) != 'undefined') && ((typeof options == 'undefined') || (typeof options[opt] == 'undefined')) )
so[opt] = $element.attr('data-' + opt);
}
},
equalise: function( resizing ) {
if ( (resizing != true) && this.element.hasClass('equalised'))
return;
var so = this.options, maxH = 0, lastLeft = -9999, colCount = 0;
var $collection = this.element.find( so.child );
$collection.css('min-height', 0);
$collection.each( function() {
if ( $(this).position().left > lastLeft) {
lastLeft = $(this).position().left;
colCount++;
} else {
return false;
}
});
if (colCount == 1) {
this.element.addClass('equalised');
return;
}
$collection.each(function() {})
for (var i=0; i < Math.ceil($collection.length/colCount); i++) {
var maxH = 0;
for (var j=0; j < colCount; j++) {
var item = $collection.get(i * colCount + j);
$(item).addClass('equalising');
if ($(item).innerHeight() > maxH) {
maxH = $(item).innerHeight();
}
}
$collection.filter('.equalising:not(.equalised)').css('min-height', maxH).removeClass('equalising').addClass('equalised');
};
this.element.addClass('equalised').find(so.child).removeClass('equalised');
}
});
/* +++ foldable +++
*
* Widget for a foldable list of items, currently used for:
* - the cast & credits list on a work page.
* - the filmography list on an agent page.
* - the alternative titles list on a work page.
* - the text in the MA-TEXT component (folding after the first paragraph).
*
* The widget is initialised by calling $('selector').foldable({options}).
*
* By default any list containing more than 10 items will be truncated after
* the tenth item and a 'Show more' button displayed.
*
* To prevent the situation where the 'show more' button only reveals one or two
* additional items, it's possible to specify the minimumReveal option, which
* sets a minimum number of items that will be hidded when the list is folded.
* For example, if the initial value is set to 3 and mimimumReveal set to 2,
* any list with 6 or more items will be truncated after the third, if the list
* contains 5 or fewer items, all items will be displayed and the foldable
* widget disabled. Setting minimumReveal to 1 (the default value) treats the
* 'initial' value as a hard limit.
*
* Example:
* $('.foldable-list-class').foldable({
* child: 'li.item-class',
* initial: 3,
* minimumReveal: 2,
* });
*/
$.widget('bfi.foldable', {
options: {
child: 'li', // The selector to identify the child elements that are foldable
initial: 10, // The initial number of items to display.
minimumReveal: 1, // The minimun number of items that will be hidden/revealed, see above.
withInfo: false, // Enable/disable the 'Showing x of y' text
lessLabel: 'Show less', // Label for the close buttons
moreLabel: 'Show more', // Label for the open buttons
bottomButtonPadding: '4em', // Padding to add to the bottom of the container element to allow for the button.
//callbacks
toggle: null
},
_create: function() {
var container = this.element;
var so = this.options;
// Initialise a property to store the number of items currently displayed.
this.currentlyShowing = 0;
var minItems = so.initial + (so.minimumReveal - 1);
// Count the number of child elements to ensure there are enough to trigger
// the foldable widget.
var count = container.find( so.child ).length;
if (count > minItems) {
// Add a can-fold class to all elements below the wrapper class
container.find("*").addClass('can-fold');
// Find the child elements that should be shown initially.
var initialSelector = (so.initial > 0) ? ':lt(' + (so.initial) + ')' : ':not(:empty)';
var initialElements = container.find(so.child + initialSelector);
this.currentlyShowing = initialElements.length;
// Remove the 'can-fold' class from:
// - (i) the child elements that should be shown initially
// - (ii) any parent elements of (i)
// - (iii) any child elements of (i)
// - (iv) any sibling elements that appear in the DOM immediately before
// any of the elements identified above (this case handles headings
// for the sections in a work's cast and credits list).
initialElements.removeClass('can-fold');
initialElements.parentsUntil( container ).removeClass('can-fold');
initialElements.find("*").removeClass('can-fold');
container.find(':not(\'.can-fold\')').prev().removeClass('can-fold');
// Add the buttons and the optional "Showing x of y" text to the top and
// bottom of the container element
markup = (so.withInfo)? 'Showing ' + this.currentlyShowing + ' of ' + count + '. ': '';
markup = '